package store

import (
	"../../deps/lessgo/utils"
	"../base"
	"regexp"
	"strconv"
	"strings"
)

const (
	NodeKeyPat   = `[a-zA-Z0-9.\-\_\/]`
	NodeDelMagic = "ukqmv4jfxyapbeqo"
	NodeDirMagic = "q6m5jwr4hv1gq7gk"

	NodeData    = "db:"
	NodeSepVal  = "i"
	NodeSepRev  = "j"
	NodeSepSize = "m"
	NodeSepNum  = "n"
	NodeSepKey  = "s"
	NodeSepDir  = "t"

	NodeTypeNil uint8 = 0
	NodeTypeDir uint8 = 1
	NodeTypeKey uint8 = 2

	EventNone                = "10"
	EventNodeCreated         = "11"
	EventNodeDeleted         = "12"
	EventNodeDataChanged     = "13"
	EventNodeChildrenChanged = "14"
)

var (
	keyFilterReg = regexp.MustCompile("/+")
)

type NodeProposal struct {
	Key string
	Val string
	Ttl int

	VerNow uint64
	VerSet uint64

	Tag  string
	Addr string

	Valued   int
	Unvalued int

	Force bool
}

type Node struct {
	P string // Path
	C string // Content
	R uint64 // Revison
	// TODO U   uint16  // uid
	// TODO G   uint16  // gid
	// TODO M   uint16  // Mode
	L int   // TTL
	T uint8 // Type
	N []Node
}

func (n Node) ReplyJson(v interface{}) error {
	return utils.JsonDecode(n.C, v)
}

func split(path string, p string) []string {
	if path == p {
		return []string{}
	}
	return strings.Split(path, p)
}

func join(parts []string, p string) string {
	return strings.Join(parts, p)
}

func nodeKeyFilter(key string) string {
	return strings.Trim(keyFilterReg.ReplaceAllString(key, "/"), "/")
}

func NodeSet(pl *NodeProposal) base.ReplyType {

	// Saving File
	key := nodeKeyFilter(pl.Key)
	preNode, _ := Cmd("HMGET", NodeData+key, NodeSepVal, NodeSepRev).List()

	keyS := split(key, "/")
	keyN := len(keyS)
	if keyN < 2 {
		return base.ReplyAccessDenied
	}

	bucket := keyS[0]
	bukKey := key[len(bucket)+1:]

	if pl.Val == NodeDelMagic {

		if preNode[1] == "" || preNode[0] == NodeDirMagic {
			return base.ReplyOK
		}

		Cmd("HDEL", NodeData+key, NodeSepVal, NodeSepRev)
		Cmd("HDEL", NodeData+join(keyS[0:keyN-1], "/"), NodeSepKey+keyS[keyN-1])

		// Statistics
		Cmd("HINCRBY", NodeData+keyS[0], NodeSepNum, -1)
		Cmd("HINCRBY", NodeData+keyS[0], NodeSepSize, -len(preNode[0]))
		Cmd("HINCRBY", NodeData, NodeSepNum, -1)
		Cmd("HINCRBY", NodeData, NodeSepSize, -len(preNode[0]))

		// binlog
		Cmd("ZADD", "lg:"+bucket, pl.VerSet, bukKey)

		return base.ReplyOK
	}

	//
	verStr := strconv.FormatUint(pl.VerSet, 10)
	if preNode[0] != pl.Val || preNode[1] != verStr {

		item := map[string]string{
			NodeSepVal: pl.Val,
			NodeSepRev: verStr,
		}

		Cmd("HMSET", NodeData+key, item)
	}
	if pl.Ttl > 0 {
		Cmd("EXPIRE", NodeData+key, pl.Ttl)
	}
	Cmd("ZADD", "lg:"+bucket, pl.VerSet, bukKey)

	// Statistics
	size := len(pl.Val)
	if preNode[1] == "" {
		Cmd("HINCRBY", NodeData+keyS[0], NodeSepNum, 1)
		Cmd("HINCRBY", NodeData, NodeSepNum, 1)
	} else {
		size = size - len(preNode[0])
	}
	if size != 0 {
		Cmd("HINCRBY", NodeData+keyS[0], NodeSepSize, size)
		Cmd("HINCRBY", NodeData, NodeSepSize, size)
	}

	// Saving DIRs
	for i := keyN - 1; i >= 0; i-- {
		subkey := join(keyS[0:i], "/")
		if i == len(keyS)-1 {
			Cmd("HSET", NodeData+subkey, NodeSepKey+keyS[i], verStr)
		} else {
			Cmd("HSET", NodeData+subkey, NodeSepDir+keyS[i], verStr)
			itemdir := map[string]string{
				NodeSepVal: NodeDirMagic,
				NodeSepRev: verStr,
			}
			dkey := nodeKeyFilter(subkey + "/" + keyS[i])
			Cmd("HMSET", NodeData+dkey, itemdir)
		}
	}

	return base.ReplyOK
}

func NodeGet(key string) (*Node, error) {

	key = nodeKeyFilter(key)

	rs := Cmd("HGETALL", NodeData+key)
	if rs.Err != nil {
		return nil, rs.Err
	}

	ttl, _ := Cmd("TTL", NodeData+key).Int()

	node := &Node{
		P: "/" + key,
		N: []Node{},
		L: ttl,
	}
	ls, _ := rs.Hash()
	for k, v := range ls {
		switch k[:1] {
		case NodeSepVal:
			if v == NodeDirMagic {
				node.T = NodeTypeDir
			} else {
				node.C = v
				node.T = NodeTypeKey
			}
		case NodeSepRev:
			if rev, e := strconv.ParseUint(v, 10, 64); e == nil {
				node.R = rev
			}
		case NodeSepDir:
			subnode := Node{
				P: node.P + "/" + k[1:],
				T: NodeTypeDir,
			}
			if rev, e := strconv.ParseUint(v, 10, 64); e == nil {
				subnode.R = rev
			}
			node.N = append(node.N, subnode)
		case NodeSepKey:
			subnode := Node{
				P: node.P + "/" + k[1:],
				T: NodeTypeKey,
			}

			if rev, e := strconv.ParseUint(v, 10, 64); e == nil {
				subnode.R = rev
			}
			node.N = append(node.N, subnode)
		}
	}
	return node, nil
}

func NodeGets(keys []string) ([]Node, error) {

	list := []Node{}

	for _, path := range keys {

		key := nodeKeyFilter(path)

		rs, err := Cmd("HMGET", NodeData+key, NodeSepRev, NodeSepVal).List()
		if err != nil {
			return list, err
		}

		if rs[1] != NodeDirMagic {

			rev, _ := strconv.ParseUint(rs[0], 10, 64)

			node := Node{
				P: "/" + key,
				C: rs[1],
				R: uint64(rev),
			}

			list = append(list, node)
		}
	}

	return list, nil
}

// func NodeList(path string) ([]Node, error) {

// 	list := []Node{}

// 	in := nodeKeyFilter(path)

// 	rs := Cmd("SMEMBERS", INodeDir+in)
// 	if rs.Err != nil {
// 		return list, rs.Err
// 	}

// 	ls, _ := rs.List()
// 	for _, v := range ls {

// 		n := Node{}
// 		switch v[0:1] {
// 		case NodeSepDir:
// 			n.T = NodeTypeDir
// 		case NodeSepFile:
// 			n.T = NodeTypeKey
// 		}
// 		n.P = path + "/" + v[1:]
// 		list = append(list, n)
// 	}

// 	return list, nil
// }
